#pragma once

#include "tcpserver.hpp"
#include "Socket.hpp"
#include "UTIL.hpp"
#include <memory>
#include <unordered_map>
#include <string>

#include "log.hpp"

using namespace MyLog;
using namespace SocketModule;

const std::string space = " ";
const std::string newline = "\r\n";
const std::string mapgap = ": ";
const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "/404.html";

class HttpRequest
{
public:
    HttpRequest() :_isInteractive(false) {}
    ~HttpRequest() {}
    std::string Serialize()
    {
        return std::string();
    }

    void PauseRequestLine(std::string &req_line)
    {
        std::stringstream ss(req_line);
        ss >> _request_method >> _uri >> _http_version;
    }

    // 服务器要对收到的http请求反序列化
    bool Deserialize(std::string &req_str)
    {
        // 获取请求报头
        std::string req_line;
        if (!util::ReadOneLine(req_str, &req_line, newline))
            return false;

        // 解析请求报头
        PauseRequestLine(req_line);

        // 判断是否指定uri
        if (_uri == "/")
            _uri = webroot + _uri + homepage;
        else
            _uri = webroot + _uri;
        //std::cout << "_requese_method " << _request_method << std::endl;
        //std::cout << "_uri            " << _uri << std::endl;
        //std::cout << "_http_version   " << _http_version << std::endl;

        // 获取请求报头
        if(_request_method == "POST" || _request_method == "post")
        {
            //std::cout << "进入交互 " << "post" << std::endl;
            _isInteractive = true;

            // 2.post方法用来上传数据
            // 其上传的使用会保存在请求的正文部分
            std::string header, f, s;
            while(true)
            {
                util::ReadOneLine(req_str, &header,newline);
                if(header == "")
                    break;

                // 分析状态报头，将其填入headers中
                size_t pos = header.find(mapgap /*: */);
                if (pos == std::string::npos)
                    return false;
                f = header.substr(0, pos);
                s = header.substr(pos + mapgap.size());
                _headers.emplace(f, s);
            }

            // 到这里请求就只剩正文部分了。
            int len = std::stoi(_headers["Content-Length"]);
            if(len >= req_str.size())
            {
                _text = _args = req_str.substr(0, len);
                //std::cout << _text << ":" << _args << std::endl;
                req_str.erase(0, len);
            }
            //std::cout << len << std::endl;
        }
        else
        {
            //std::cout << "进入交互 " << "get" << std::endl;
            //std::cout << "uri.size()->" << _uri.size() << std::endl;

            // 1.GET方法一般是用来获取静态网页/资源，但其也可以用作上传数据
            // 当使用get方法上传资源时，参数将被提到uri中
            // 从http requese中获取的uir----> ./wwwroot/login.html?username=zhangsan&password=1111111
            auto pos = _uri.find("?");
            //std::cout << "pos->" << pos << std::endl;
            if(pos != std::string::npos)
            {
                _isInteractive = true;
                _args = _uri.substr(pos + 1); // username=zhangsan&password=1111111
                _uri = _uri.substr(0, pos);   // ./wwwroot/login.html ????????????????????????????????
                //std::cout << "uri" << _uri << std::endl;
                //std::cout << "args" << _args << std::endl;
            }
        }


        return true;
    }

    std::string Uri() { return _uri; }
    bool IsInteractive() { return _isInteractive; }
    std::string Args() { return _args; }

private:
    std::string _request_method;
    std::string _uri;
    std::string _http_version;

    std::string _blankline;
    std::unordered_map<std::string, std::string> _headers;
    std::string _text;

    bool _isInteractive;
    std::string _args;
};

class HttpResponse
{
public:
    HttpResponse() {}
    ~HttpResponse() {}
    std::string Serialize()
    {
        // 将结构化的数据序列化成一个大字符串
        std::string state_line = _http_version + space + std::to_string(_code) + space + _code_describe + newline;
        std::string state_headers;
        for (auto &header : _headers)
        {
            std::string line = header.first + mapgap + header.second + newline;
            state_headers += line;
        }

        for(auto &p : _cookie)
        {
            std::string line = p.first + mapgap + p.second + newline;
            state_headers += line;
        }

        return state_line + state_headers + newline + _text;
    }

    bool Deserialize() { return false; }

    void SetTargetFile(std::string &&target) { _targetfile = target; }

    void SetHeaders(std::string &&key, std::string &&value)
    {
        auto iter = _headers.find(key);
        if(iter != _headers.end()) return;
        _headers.emplace(key, value);
    }

    void SetCode(int code)
    {
        _code = code;
        switch (code)
        {
        case 200:
            _code_describe = "OK";
            break;
        case 404:
            _code_describe = "Not Fount";
            break;
        case 301:
            _code_describe = "Moved permanently";
        case 302:
            _code_describe = "See Other";
        default:
            break;
        }
    }

    std::string Uri2Suffix(const std::string &filename)
    {
        // 提取客户端要访问的资源文件的后缀
        auto pos = filename.rfind('.');
        if(pos == std::string::npos) return "";
        std::string suffix = filename.substr(pos+1);

        // 将该后缀转换为content-type格式
        if(suffix == "html")
            return "text/html";
        else if(suffix == "png")
            return "image/png";
        else if(suffix == "jpg")
            return "image/jpeg";
        else
            return "";
    }

    void SetText(std::string &s) {_text = s; }

    void SetCookie(std::string &&args)
    {
        _cookie.emplace_back("Set-Cookie", args);
    }

    void MakeResponse(HttpRequest &req)
    {
        // .ico文件，用来加载网页的小图标
        //if(_targetfile == "./wwwroot/favicon.ico")
        //{
        //    LOG(loglevel::DEBUG) << "client want get: " << _targetfile << " ignore"; 
        //    return;
        //}

        if(_targetfile == "./wwwroot/redirect")
        {
            // for test redirct
            // 如果访问的资源是redirect，我们将网页重定向到哔站
            
            // 302 临时重定向，常用于登录，登录完成之后，又返回到原来的网页
            // 临时重定向并不会修改之前的请求方法, 只是暂时跳转到新的url，原url仍然有效
            //SetCode(302);
            //SetHeaders("Location", "https://www.bilibili.com/");

            // 301 永久重定向, 一般用作更换域名
            // 表示原 URL 对应的资源已永久迁移至新 URL，客户端和搜索引擎应永久使用新 URL，后续请求直接访问新地址，原 URL 可废弃。
            // 可能会改变请求方法
            SetCode(301);

            // Location: 响应报头的内容，表明，接下来要重定向到哪一个网页。
            SetHeaders("Location", "https://www.bytedance.com/");
            return;
        }

        if (util::ReadFileContext(_targetfile, &_text))
        {
            // 访问的资源是存在的
            //std::cout << "targetfile->" << _targetfile << std::endl;
            SetCode(200);
        }
        else
        {
            // 访问的资源不存在，重新打开404页面，返回给客户端
            _targetfile = webroot + page_404; // ./wwwroot/404.html
            //std::cout << "targetfile->" << _targetfile << std::endl;
            util::ReadFileContext(_targetfile, &_text);
            SetCode(404);

            // 有了重定向，我们就可以直接在客户端访问不存在的资源时，将页面重定向到404网页
            //SetCode(302);
            //SetHeaders("Location", "http://139.9.134.152:8080/404.html");
            //return;
        }
        // content-length : 用来表示发送的响应正文的长度
        SetHeaders("Content-Length", std::to_string(util::GetFileLength(_targetfile)));

        // content-type : 用来表示客户端申请的资源的类型
        SetHeaders("Content-Type", Uri2Suffix(_targetfile));

        // 因为http协议是一个无状态的协议，他不会保存用户的信息。
        // 例如登录是视频网站，登录一次后，访问里面的内容，因为http不会记录客户端信息，所以需要用户再次输入账号密码
        // 为了解决这样的问题，在http请求时，提交一次username，passwd时，服务端在响应时，会通过Set-Cookie将这个信息写回客户端的cookie文件中
        // 自此，客户端请求的时候，请求报头就会自动包含cookie文件里面的内容，以cookie： username方法，发送给服务端
        // 服务端就可以拿着cookie进行登录验证，如果发现你是用户，就直接认证通过，进入网页
        // 如果通过cookie验证失败，则会返回登录页面。
        SetCookie("username=zhangsan");
        SetCookie("passwd=111111");
        SetCookie("age=20");
        SetCookie("city=xian");

        // 但只是用cookie的方式会导致两个问题
        // 1.你本地浏览器上的cookie文件可能会被盗用，这样其他人就可以拿着你的cookie，以你的身份访问服务端
        // 2.cookie文件对内容做了编码，但依旧是可见的，所以，你的隐私会泄露

        // 为了解决上面的问题，现在通常采用cookie与session一起
        // 当我们登录的时候，服务端那我们的登录信息，会在服务器内部创建一个临时文件session，该文件有一个session_id，是唯一的。
        // 服务端会将该session通过Set-Cookie写回客户端。自此，客户端再访问的时候，就会借助session_id来进行访问
        // 服务端再拿到session_id之后，在其内部判断该session_id是否存在，如果存在登录成功，失败，则重新进行登录
        
        // 当然，借助session只能解决你的隐私信息不被泄露
        // seesion_id依旧有可能被他人获取，进行访问。
    }

public:
    std::string _http_version;
    int _code;
    std::string _code_describe;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;

    std::vector<std::pair<std::string, std::string>> _cookie;

    std::string _targetfile; // 客户端请求的资源路径
};

// 底层基于tcp的http服务器

using http_t = std::function<void(HttpRequest &req, HttpResponse &resp)>;
class Httpserver
{
public:
    Httpserver(uint16_t port)
        : _httpsvr(std::make_unique<TcpServer>(port))
    {
    }

    bool Decode(std::string req_str)
    {
        // 判断是否读到请求行/状态行
        std::string req_line = util::ReadOneLine(req_str, newline /*\r\n*/);
        if (req_line == "")
            return false;

        // 读取状态报头
        std::unordered_map<std::string, std::string> headers;
        std::string line, f, s;
        while (!req_str.empty())
        {
            // 获取一行
            line = util::ReadOneLine(req_str, newline /*\r\n*/);

            // 如果该行是空行，则退出，转而去判断正文长度, 此时req_str中只剩正文部分(如果存在正文)
            if (line.empty())
                break;

            // 分析状态报头，将其填入headers中
            size_t pos = line.find(mapgap /*: */);
            if (pos == std::string::npos)
                return false;
            f = line.substr(0, pos);
            s = line.substr(pos + mapgap.size());
            headers.emplace(f, s);
        }

        // 查找状态报头中是否包含Content-Length
        auto iter = headers.find("Content-Length");
        if (iter == headers.end())
            return true;
        int content_length = std::stoi(iter->second);

        // 判断正文长度是否满足content-length
        if (content_length != req_str.size())
            return false;
        return true;
    }

    void HttpHandler(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 1.获取请求报文
        std::string req_str;
        int n = sock->Recv(&req_str);
        if(n > 0)
        {
            // Decode用来判断是否收到了完整的报文
            //if (!Decode(req_str))
            //    return;
            HttpRequest req;
            std::cout << "-----------------------------------------------" << std::endl;
            std::cout << req_str;
            std::cout << "-----------------------------------------------" << std::endl;
            req.Deserialize(req_str);

            // 反序列化之后，判断是否需要交互，调用服务接口
            HttpResponse resp;
            resp._http_version = "HTTP/1.1";
            resp._blankline = newline;
            if(req.IsInteractive())
            {
                //std::cout << "准备调用服务路由" << std::endl;
                if(_rounite.find(req.Uri()) == _rounite.end())
                {
                    // 不存在对应的服务接口
                    //std::cout << "不存在对应功能路由" << std::endl;
                }
                else
                {
                    //std::cout << "预备调用" << std::endl;
                    _rounite[req.Uri()](req, resp);
                }
            }
            else
            {
                resp.SetTargetFile(req.Uri());
                resp.MakeResponse(req);
            }

            std::string resp_str = resp.Serialize();
            std::cout << "-----------------------------------------------" << std::endl;
            std::cout << resp_str << std::endl;
            std::cout << "-----------------------------------------------" << std::endl;
            // 2.发送响应
            sock->Send(resp_str);

            //std::cout << "-----------------------------------------------" << std::endl;
            //std::cout << resp_str << std::endl;
            //std::cout << "-----------------------------------------------" << std::endl;
        }


// #ifndef DEBUG
// #define DEBUG
#ifdef DEBUG
        // 假设收到的就是完整的报文
        // std::string req_str;
        // sock->Recv(&req_str);

        //// 测试给客户端进行响应
        // HttpResponse resq;
        // resq._http_version = "HTTP/1.1";
        // resq._code = 200;
        // resq._code_describe = "OK";
        // resq._blankline = newline;

        //// http用户发送请求，我们默认打开web根目录下的index.html网页，即首页
        // std::string file = webroot + "/" + homepage;
        // util::ReadFileContext(file, &(resq._text));

        //// 序列化
        // std::string sendstr = resq.Serialize();
        // std::cout << sendstr << std::endl;
        // sock->Send(sendstr);

        // resq._text = R"(
        //                 <!DOCTYPE html>
        //                 <html lang="en">
        //                 <head>
        //                     <meta charset="UTF-8">
        //                     <meta name="viewport" content="width=device-width, initial-scale=1.0">
        //                     <title>Hello, World!</title>
        //                 </head>
        //                 <body>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                     <h1>Hello, World!</h1>
        //                 </body>
        //                 </html>)";
#endif
    }

    void Start()
    {
        _httpsvr->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                        { this->HttpHandler(sock, client); });
    }

    ~Httpserver() {}

    // name -> login
    void RegistService(std::string name, http_t h)
    {
        // key -> ./wwwroot/login
        std::string key = webroot + name;
        //std::cout << key << std::endl;
        auto iter = _rounite.find(key);
        if(iter != _rounite.end())
            return;
        _rounite.emplace(key, h);
    }

private:
    std::unique_ptr<TcpServer> _httpsvr;
    std::unordered_map<std::string, http_t> _rounite;
};